﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace Tzync
{
    /******************************************************************************************************************/
    /* Partial Class to Analyze the Guest System's files and its LogFile and find Conflicting/Non-Conflicting Updates */
    /******************************************************************************************************************/

    partial class Analyze
    {
        //Description     : System B (or Host) specific function. Given a list of files and corresponding log entry 
        // TreeNodes, this function simply computes the differences between them and adds to the
        // main list of differences.
        //Pre-Conditions  : sysFiles, fileChilds are not NULL, nodeParent is proper.
        //Post-Conditions : The list of differences maintained in this class is updated with differences in these
        // set of files.

        private void findFileDifferenceB(FileInfo[] sysFiles, List<int> fileChilds, int nodeParent)
        {
            if (sysFiles == null) throw new ArgumentNullException("sysFiles");
            if (fileChilds == null) throw new ArgumentNullException("fileChilds");


            Pair<DirectionType, OperationType>[] fileStatus = new Pair<DirectionType, OperationType>[sysFiles.Count()];
            int sysSize = sysFiles.Count(), partnerSize = fileChilds.Count(), partnerIndex;
            List<String> nodeFileNames = new List<string>();
            bool[] partnerFileVisited = new bool[fileChilds.Count()];
            DateTimeParser dtpFile;
            int vA_f, vA_s, vB_f, vB_s;

            for (int i = 0; i < sysSize; i++)
            { fileStatus[i] = new Pair<DirectionType, OperationType>(); }
            for (int i = 0; i < partnerSize; i++)
            { partnerFileVisited[i] = false; nodeFileNames.Add(arrNode[fileChilds[i]].getName()); }

            for (int i = 0; i < sysSize; i++)
            {
                partnerIndex = nodeFileNames.BinarySearch(sysFiles[i].Name);                

                if (partnerIndex >= 0)
                {
                    vA_f = arrNode[fileChilds[partnerIndex]].getVersionA().First;
                    vA_s = arrNode[fileChilds[partnerIndex]].getVersionA().Second;
                    vB_f = arrNode[fileChilds[partnerIndex]].getVersionB().First;
                    vB_s = arrNode[fileChilds[partnerIndex]].getVersionB().Second;

                    partnerFileVisited[partnerIndex] = true;
                    dtpFile = new DateTimeParser(sysFiles[i].LastWriteTime);
                    arrNode[fileChilds[partnerIndex]].setFullPathB(sysFiles[i].FullName);

                    //If system file is dirty                            
                    if (DateTime.Compare(dtpFile.GetNewDateTime(), arrNode[fileChilds[partnerIndex]].getSyncB()) != 0)
                    {                        
                        //If partner file is also dirty -- CONFLICTING UPDATE
                        if (arrNode[fileChilds[partnerIndex]].getVersionA().First != arrNode[fileChilds[partnerIndex]].getVersionB().First)
                        {
                            fileStatus[i].First = DirectionType.Nil;
                            //Detecting Modification(Here)-Deletion(There) Conflict
                            if (vA_f == -1 && vA_s == 0)
                                fileStatus[i].Second = OperationType.ModDeleteConflict;
                            //Detecting Creation-Creation(First Detected there) Conflict
                            else if (vA_f == 1 && vA_s == 0 && vB_f == 0 && vB_s == 0)
                                fileStatus[i].Second = OperationType.CreationCreationConflict;
                            //Detecting Mod-Mod Conflict
                            else fileStatus[i].Second = OperationType.ModModConflict; 
                        }
                        //If its an update only in this system -- NON-CONFLICTING UPDATE FROM THIS SIDE
                        else { fileStatus[i].First = DirectionType.SystemToHD; fileStatus[i].Second = OperationType.Modification; }

                        AddDifference(new Difference(sysFiles[i], arrNode[fileChilds[partnerIndex]], fileStatus[i]));
                    }

                    //Else if only partner file might be dirty 
                    else
                    {
                        //Check if partner File is dirty! -- NON-CONFLICTING UPDATE FROM PARTNER SIDE
                        if (arrNode[fileChilds[partnerIndex]].getVersionA().First != arrNode[fileChilds[partnerIndex]].getVersionB().First)
                        {
                            fileStatus[i].First = DirectionType.HDToSystem;
                            if (arrNode[fileChilds[partnerIndex]].getVersionA().First != -1)
                            {
                                fileStatus[i].Second = OperationType.Modification;
                            }
                            //If the file is deleted in the partner side
                            else
                            {
                                fileStatus[i].Second = OperationType.Deletion;
                            }
                            AddDifference(new Difference(sysFiles[i], arrNode[fileChilds[partnerIndex]], fileStatus[i]));
                        }
                        //Else Identical files.
                        else 
                        { 
                            fileStatus[i].First = DirectionType.Nil; 
                            fileStatus[i].Second = OperationType.None; 
                        }
                    }                    
                }

                //NON-CONFLICTING CREATION IN THIS SIDE
                if (partnerIndex < 0)
                {                
                    TreeNode N = treeHandler.fileUpdateArrNode(sysFiles[i], nodeParent);
                    fileStatus[i].First = DirectionType.SystemToHD; fileStatus[i].Second = OperationType.Creation;
                    AddDifference(new Difference(sysFiles[i], N, fileStatus[i]));                    
                }
            }

            //CHECK FOR (DELETIONS HERE)/(CREATIONS IN PARTNER SIDE)
            for (int i = 0; i < partnerSize; i++)
            {
                if (partnerFileVisited[i] == false && fileChilds[i] != -1)
                {
                    Pair<DirectionType, OperationType> f = new Pair<DirectionType, OperationType>();
                    vA_f = arrNode[fileChilds[i]].getVersionA().First;
                    vA_s = arrNode[fileChilds[i]].getVersionA().Second;
                    vB_f = arrNode[fileChilds[i]].getVersionB().First;
                    vB_s = arrNode[fileChilds[i]].getVersionB().Second;

                    //NON-CONFLICTING CREATION IN THE PARTNER SIDE
                    if (arrNode[fileChilds[i]].getVersionB().First == 0 && arrNode[fileChilds[i]].getVersionB().Second == 0)
                    {
                        f.First = DirectionType.HDToSystem; f.Second = OperationType.Creation;
                        String newAddress = Path.Combine(arrNode[nodeParent].getFullPathB(), arrNode[fileChilds[i]].getName());
                        arrNode[fileChilds[i]].setFullPathB(newAddress);                       

                        AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathB()), arrNode[fileChilds[i]], f));
                    }
                    //CONFLICTING (DELETION HERE)/UPDATE THERE
                    else if (arrNode[fileChilds[i]].getVersionB().First != arrNode[fileChilds[i]].getVersionA().First)
                    {
                        f.First = DirectionType.Nil;
                        //If Deletion was first detected in A (If A has also deleted)
                        if (arrNode[fileChilds[i]].getVersionA().First == -1 && arrNode[fileChilds[i]].getVersionA().Second == 0)
                        {
                            if (arrNode[fileChilds[i]].getVersionB().First == 0 && arrNode[fileChilds[i]].getVersionB().Second == -1)
                                f.Second = OperationType.None;
                            else
                            {
                                f.Second = OperationType.DeletionDeletionConflict;
                                AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathB()), arrNode[fileChilds[i]], f));
                            }
                        }
                        //If A just modified 
                        else
                        {
                            //If a ModDelete conflict was resolved in system A in previous sync, to set it up as 
                            //Modification
                            if (vB_f == 0 && vB_s == -1 && vA_f != -1 && vA_s != -1)
                            {
                                f.First = DirectionType.HDToSystem;
                                f.Second = OperationType.Modification;
                            }                            
                            //Normal Mod-Delete Conflict
                            else
                            {
                                f.Second = OperationType.ModDeleteConflict;
                            }                            
                            AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathB()), arrNode[fileChilds[i]], f));
                        }
                    }
                    //NON-CONFLICTING DELETION HERE
                    else
                    {
                        //Already deleted in both places long time ago
                        if (vB_f == vA_f && vB_s == vA_s && (vB_f == -1 || vB_s == -1))
                            f.Second = OperationType.None;
                        else
                        {
                            f.Second = OperationType.Deletion;
                            f.First = DirectionType.SystemToHD;
                            AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[i]].getFullPathB()), arrNode[fileChilds[i]], f));
                        }
                    }
                }

            }   //2nd for loop ends

        }

        //Description     : System B (or Guest) specific function. Given a list of folders and corresponding log entry 
        // TreeNodes, this function computes the differences between them by recursively calling
        // findDiff() method.
        //Pre-Conditions  : sysDirectories, directoryChilds are not NULL, nodeParent is proper.
        //Post-Conditions : The list of differences maintained in this class is updated with differences in these
        // set of files.

        private void findFolderDifferenceB(DirectoryInfo[] sysDirectories, List<int> directoryChilds, int nodeParent)
        {
            if (sysDirectories == null) throw new ArgumentNullException("sysDirectories");
            if (directoryChilds == null) throw new ArgumentNullException("directoryChilds");


            Pair<DirectionType, OperationType>[] folderStatus = new Pair<DirectionType, OperationType>[sysDirectories.Count()];
            int sysSize = sysDirectories.Count(), partnerSize = directoryChilds.Count(), partnerIndex;
            List<String> nodeDirectoryNames = new List<string>();
            bool[] partnerFolderVisited = new bool[directoryChilds.Count()];
            DateTimeParser dtpFile;
            int vA_f, vA_s, vB_f, vB_s;

            for (int i = 0; i < sysSize; i++)
            { folderStatus[i] = new Pair<DirectionType, OperationType>(); }
            for (int i = 0; i < partnerSize; i++)
            { partnerFolderVisited[i] = false; nodeDirectoryNames.Add(arrNode[directoryChilds[i]].getName()); }

            for (int i = 0; i < sysSize; i++)
            {
                partnerIndex = nodeDirectoryNames.BinarySearch(sysDirectories[i].Name);                

                if (partnerIndex >= 0)
                {
                    vA_f = arrNode[directoryChilds[partnerIndex]].getVersionA().First;
                    vA_s = arrNode[directoryChilds[partnerIndex]].getVersionA().Second;
                    vB_f = arrNode[directoryChilds[partnerIndex]].getVersionB().First;
                    vB_s = arrNode[directoryChilds[partnerIndex]].getVersionB().Second;

                    partnerFolderVisited[partnerIndex] = true;
                    dtpFile = new DateTimeParser(sysDirectories[i].LastWriteTime);
                    arrNode[directoryChilds[partnerIndex]].setFullPathB(sysDirectories[i].FullName);

                    //If system file is dirty                            
                    if (DateTime.Compare(dtpFile.GetNewDateTime(), arrNode[directoryChilds[partnerIndex]].getSyncB()) != 0)
                    {                                                
                        //If partner file is also dirty -- CONFLICTING UPDATE
                        if (arrNode[directoryChilds[partnerIndex]].getVersionA().First != arrNode[directoryChilds[partnerIndex]].getVersionB().First)
                        {
                            folderStatus[i].First = DirectionType.Nil;
                            //Detecting Modification(Here)-Deletion(There) Conflict
                            if (vA_f == -1 && vA_s == 0)
                                folderStatus[i].Second = OperationType.ModDeleteConflict;
                            //Detecting Creation-Creation Conflict
                            else if (vA_f == 1 && vB_s == 0 && vB_f == 0 && vB_s == 0)
                                folderStatus[i].Second = OperationType.CreationCreationConflict;
                            //Detecting Mod-Mod Conflict 
                            else folderStatus[i].Second = OperationType.ModModConflict;
                        }
                        //If its an update only in this system -- NON-CONFLICTING UPDATE FROM THIS SIDE
                        else { folderStatus[i].First = DirectionType.SystemToHD; folderStatus[i].Second = OperationType.Modification; }
                        AddDifference(new Difference(sysDirectories[i], arrNode[directoryChilds[partnerIndex]], folderStatus[i]));
                    }

                    //Else if only partner file might be dirty 
                    else
                    {                        
                        //Check if partner File is dirty! -- NON-CONFLICTING UPDATE FROM PARTNER SIDE
                        if (arrNode[directoryChilds[partnerIndex]].getVersionA().First != arrNode[directoryChilds[partnerIndex]].getVersionB().First)
                        {
                            folderStatus[i].First = DirectionType.HDToSystem;
                            if (arrNode[directoryChilds[partnerIndex]].getVersionA().First != -1)
                            {
                                folderStatus[i].Second = OperationType.Modification;
                            }
                            else
                            {
                                folderStatus[i].Second = OperationType.Deletion;
                            }
                            AddDifference(new Difference(sysDirectories[i], arrNode[directoryChilds[partnerIndex]], folderStatus[i]));
                        }
                        //Else Identical files.
                        else
                        {
                            folderStatus[i].First = DirectionType.Nil; 
                            folderStatus[i].Second = OperationType.None;
                        }
                    }

                    //Responsibility of Sync Class.
                    dtpFile = new DateTimeParser(sysDirectories[i].LastWriteTime);
                    arrNode[directoryChilds[partnerIndex]].setSyncB(dtpFile.GetNewDateTime());

                    findDiff(sysDirectories[i], arrNode[directoryChilds[partnerIndex]]);
                }

                //NON-CONFLICTING CREATION IN THIS SIDE
                if (partnerIndex < 0)
                {
                    TreeNode N = treeHandler.folderUpdateArrNode(sysDirectories[i], nodeParent);
                    folderStatus[i].First = DirectionType.SystemToHD; folderStatus[i].Second = OperationType.Creation;
                    AddDifference(new Difference(sysDirectories[i], N, folderStatus[i]));
                    findDiff(sysDirectories[i], N);                    
                }
            }

            //CHECK FOR (DELETIONS HERE)/(CREATIONS IN PARTNER SIDE)
            for (int i = 0; i < partnerSize; i++)
            {
                if (partnerFolderVisited[i] == false && directoryChilds[i] != -1)
                {
                    vA_f = arrNode[directoryChilds[i]].getVersionA().First;
                    vA_s = arrNode[directoryChilds[i]].getVersionA().Second;
                    vB_f = arrNode[directoryChilds[i]].getVersionB().First;
                    vB_s = arrNode[directoryChilds[i]].getVersionB().Second;
                    Pair<DirectionType, OperationType> f = new Pair<DirectionType, OperationType>();
                    //NON-CONFLICTING CREATION IN THE PARTNER SIDE
                    if (arrNode[directoryChilds[i]].getVersionB().First == 0 && arrNode[directoryChilds[i]].getVersionB().Second == 0)
                    {
                        f.First = DirectionType.HDToSystem; f.Second = OperationType.Creation;
                        String newAddress = Path.Combine(arrNode[nodeParent].getFullPathB(), arrNode[directoryChilds[i]].getName());
                        arrNode[directoryChilds[i]].setFullPathB(newAddress);                        

                        AddDifference(new Difference(new DirectoryInfo(arrNode[directoryChilds[i]].getFullPathB()), arrNode[directoryChilds[i]], f));
                        recurseDirectoryNode(arrNode[directoryChilds[i]], f, 0);
                    }
                    //CONFLICTING (DELETION HERE)/UPDATE THERE
                    else if (arrNode[directoryChilds[i]].getVersionB().First != arrNode[directoryChilds[i]].getVersionA().First)
                    {
                        f.First = DirectionType.Nil;
                        //If Deletion was first detected in A (If A has also deleted)
                        if (arrNode[directoryChilds[i]].getVersionA().First == -1 && arrNode[directoryChilds[i]].getVersionA().Second == 0)
                        {
                            if (arrNode[directoryChilds[i]].getVersionB().First == 0 && arrNode[directoryChilds[i]].getVersionB().Second == -1)
                                f.Second = OperationType.None;
                            else
                            {
                                f.Second = OperationType.DeletionDeletionConflict;
                                AddDifference(new Difference(new FileInfo(@arrNode[directoryChilds[i]].getFullPathB()), arrNode[directoryChilds[i]], f));
                            }
                        }
                        //If A just modified 
                        else
                        {                            
                            if (vB_f == 0 && vB_s == -1 && vA_f != -1 && vA_s != -1)
                            {
                                f.First = DirectionType.HDToSystem;
                                f.Second = OperationType.Modification;
                            }
                            //CHECK FOR CORRECTNESS
                            else
                            {
                                f.Second = OperationType.ModDeleteConflict;                                
                            }                            
                            AddDifference(new Difference(new FileInfo(@arrNode[directoryChilds[i]].getFullPathB()), arrNode[directoryChilds[i]], f));
                        }
                    }
                    //NON-CONFLICTING DELETION HERE
                    else
                    {
                        //Already deleted in both places long time ago
                        if (vB_f == vA_f && vB_s == vA_s && (vB_f == -1 || vB_s == -1))
                            f.Second = OperationType.None;
                        else
                        {
                            f.Second = OperationType.Deletion;
                            f.First = DirectionType.SystemToHD;
                            AddDifference(new Difference(new DirectoryInfo(arrNode[directoryChilds[i]].getFullPathB()), arrNode[directoryChilds[i]], f));
                        }
                        recurseDirectoryNode(arrNode[directoryChilds[i]], f, 1);
                    }
                }
            }
        }

        //Description     : In cases of Deletion in this system or creation in partner system, a need arises to recurse 
        // the tree built from metadata to mark very file in the sub-tree to be deleted/created in the
        // respective system (Host or Guest). This function does this.
        //Pre-Conditions  : N and f are not NULL and systemID is correct.
        //Post-Conditions : Non-Conflicting Creation/ Conflicting Deletion/ Non-Conflicting Deletion differences are 
        // are found and added to the main list of Differences.

        private void recurseDirectoryNode(TreeNode N, Pair<DirectionType, OperationType> f, int systemID)
        {
            List<int> fileChilds, folderChilds;
            String newAddress;
            int nodeParent = N.getId();

            fileChilds = treeHandler.getFileChilds(N);
            folderChilds = treeHandler.getFolderChilds(N);

            if (systemName.CompareTo(SystemType.Host) == 0)
            {
                for (int j = 0; j < fileChilds.Count(); j++)
                {
                    newAddress = Path.Combine(arrNode[nodeParent].getFullPathA(), arrNode[fileChilds[j]].getName());
                    arrNode[fileChilds[j]].setFullPathA(newAddress);
                    if (systemID == 0 && f.Second!=OperationType.None)
                    {                        
                        AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[j]].getFullPathA()), arrNode[fileChilds[j]], f));
                    }
                    else if (f.Second != OperationType.None)
                    {                        
                        AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[j]].getFullPathB()), arrNode[fileChilds[j]], f));
                    }
                }

                for (int j = 0; j < folderChilds.Count(); j++)
                {
                    newAddress = Path.Combine(arrNode[nodeParent].getFullPathA(), arrNode[folderChilds[j]].getName());
                    arrNode[folderChilds[j]].setFullPathA(newAddress);
                    if (systemID == 0 && f.Second!=OperationType.None)
                        AddDifference(new Difference(new DirectoryInfo(arrNode[folderChilds[j]].getFullPathA()), arrNode[folderChilds[j]], f));
                    else if (f.Second != OperationType.None)
                        AddDifference(new Difference(new DirectoryInfo(arrNode[folderChilds[j]].getFullPathB()), arrNode[folderChilds[j]], f));

                    recurseDirectoryNode(arrNode[folderChilds[j]], f, systemID);    //Recurse here
                }
            }

            else if (systemName.CompareTo(SystemType.Guest) == 0)
            {
                for (int j = 0; j < fileChilds.Count(); j++)
                {
                    newAddress = Path.Combine(arrNode[nodeParent].getFullPathB(), arrNode[fileChilds[j]].getName());
                    arrNode[fileChilds[j]].setFullPathB(newAddress);
                    if (systemID == 0 && f.Second != OperationType.None)
                        AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[j]].getFullPathA()), arrNode[fileChilds[j]], f));
                    else if (f.Second != OperationType.None)
                        AddDifference(new Difference(new FileInfo(@arrNode[fileChilds[j]].getFullPathB()), arrNode[fileChilds[j]], f));
                }

                for (int j = 0; j < folderChilds.Count(); j++)
                {
                    newAddress = Path.Combine(arrNode[nodeParent].getFullPathB(), arrNode[folderChilds[j]].getName());
                    arrNode[folderChilds[j]].setFullPathB(newAddress);
                    if (systemID == 0 && f.Second != OperationType.None)
                        AddDifference(new Difference(new DirectoryInfo(arrNode[folderChilds[j]].getFullPathA()), arrNode[folderChilds[j]], f));
                    else if (f.Second != OperationType.None)
                        AddDifference(new Difference(new DirectoryInfo(arrNode[folderChilds[j]].getFullPathB()), arrNode[folderChilds[j]], f));

                    recurseDirectoryNode(arrNode[folderChilds[j]], f, systemID);    //Recurse here
                }
            }
        }                
    }

    /******************************/
    /* Partial Class Analyze Ends */
    /******************************/
}
